
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Xml;
using Swaf.Crypto;

namespace Swaf.Crypto
{
    public class X509XmlCrypto : IX509XmlCrypto
    {
        #region Member Variables

        private IDictionary<String, ICertificateInfo> _certInfos = new Dictionary<String, ICertificateInfo>();

        #endregion

        #region Constructors

        public X509XmlCrypto()
        {
        }

        #endregion

        #region IX509XmlCrypto Members

        public IDictionary<String, ICertificateInfo> Certificates
        {
            get { return _certInfos; }
        }

        public string Encrypt(string certNameOrSubject, string elementName, string clearElementString)
        {
            XmlElement encDataElem = null;

            Encrypt(certNameOrSubject, elementName, clearElementString, out encDataElem);
            Debug.Assert(encDataElem != null);

            return encDataElem.OuterXml;
        }

        public void Encrypt(string certNameOrSubject, XmlTextWriter writer, string elementName, string clearElementString)
        {
            String encryptElementString = Encrypt(certNameOrSubject, elementName, clearElementString);

            writer.WriteRaw(encryptElementString);
        }

        public void Encrypt(string certNameOrSubject, string elementName, string clearElementString, out XmlElement encXmlElement)
        {
            // Create a temp XmlElement to do the encryption on
            XmlDocument tmpDoc = new XmlDocument();
            XmlElement tmpElem = tmpDoc.CreateElement(elementName);
            tmpElem.InnerText = clearElementString;

            // Encrypt this element that was created
            encXmlElement = Encrypt(certNameOrSubject, tmpElem);
        }

        public XmlElement Encrypt(string certNameOrSubject, XmlElement clearElement)
        {
            X509Certificate2 publicCert = FindCertificate(certNameOrSubject);

            // ensure requested certificate is registered with CSF
            if (publicCert == null)
            {
                throw new CryptoException(String.Format("Unable to locate registration of certificate [{0}].", certNameOrSubject));
            }

            // Create the EncryptedXml class for Xml encryption
            EncryptedXml encXml = new EncryptedXml();

            // Get the EncryptedData representation of the element
            EncryptedData encData = encXml.Encrypt(clearElement, publicCert);

            // Get the Xml representation of EncryptedData
            XmlElement encXmlElement = encData.GetXml();

            return encXmlElement;
        }

        public string Decrypt(string encryptedElementString)
        {
            throw new NotImplementedException("The method or operation is not implemented.");
        }

        /// <summary>
        /// The certificate specified in the Decrypt function assumes that the certificate is in a 
        /// certificate store.  This means it is either in the store for the running user or in the
        /// local machine store.
        /// </summary>
        /// <param name="docWithEncryptedData">XmlDocument that has EncryptedData xml elements in it</param>
        public void Decrypt(XmlDocument docWithEncryptedData)
        {
            // Load DOM into EncryptedXML
            EncryptedXml encXmlOfDocToDecrypt = new EncryptedXml(docWithEncryptedData);

            // Decrypt the document
            encXmlOfDocToDecrypt.DecryptDocument();
        }

        #endregion

        #region Helpers

        protected X509Certificate2 FindCertificate(String subject)
        {
            // First check the registered file certificates with CSF
            if (Certificates.ContainsKey(subject))
            {
                // Locate the certificate
                ICertificateInfo certInfo = Certificates[subject];

                // Load the certificate
                X509Certificate2 cert = new X509Certificate2(certInfo.PublicCertPath);

                return cert;
            }

            // Didn't find it so locate in the well known certificate stores on this machine
            StoreLocation[] storeLocations = new StoreLocation[] { StoreLocation.LocalMachine, StoreLocation.CurrentUser };

            StoreName[] storeNames = new StoreName[] { StoreName.AddressBook, StoreName.AuthRoot, StoreName.CertificateAuthority, StoreName.My,
                StoreName.Root, StoreName.TrustedPeople, StoreName.TrustedPublisher };

            foreach (StoreLocation storeLocation in storeLocations)
            {
                foreach (StoreName storeName in storeNames)
                {
                    try
                    {
                        // Get store
                        X509Store store = new X509Store(storeName, storeLocation);
                        // Open store for read access
                        store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
                        try
                        {
                            foreach (X509Certificate2 cert in store.Certificates)
                            {
                                // Find the first cert that has the subject as its name
                                if (String.Compare(cert.Subject, subject, true) == 0)
                                {
                                    return cert;
                                }
                            }
                        }
                        finally
                        {
                            store.Close();
                        }
                    }
                    catch (Exception e)
                    {
                        // This type of store doesn't exist in the type of location requested
                        // continue on
                    }
                }
            }

            return null;
        }

        #endregion
    }
}
